/*
SteelNzb
Copyright (C) 2008  Wessel v. Staal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "global.h"
#include "remotectrl.h"
#include "socket.h"
#include "remoteproto.h"
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h> 
#include <pthread.h>

#include <libxml/xmlreader.h>

#if defined(WIN32) || defined(WINCE)
//#include <winsock2.h>
#include <windows.h>

#define stricmp _stricmp
#define unlink(x) DeleteFileA(x)
#ifndef WINCE
#include <direct.h>
#endif
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <dirent.h>
#define stricmp strcasecmp
#endif

void* remotectrl_listenThread(void* remotecontrol);
int remotectrl_createServerSocket(u_short port);
void remotectrl_handleClientSocket(void* remotecontrol);

typedef struct remoteControlState
{
	void* stats;
	int listenSocket;
	int clientSocket;
	int port;
	void (*remoteControlCommand)(void*, char*, char*);
	void (*fileReceivedCallback)(void*, char* filename, char* path, long size);
	pthread_t thread;
	void* userData;
	char version[10];
	char tmpDir[1024];
	pthread_mutex_t mutex;
} remoteControlState;

void* remotectrl_create(int port, void* stats)
{
	remoteControlState * state = NULL;

	if ( (state = malloc(sizeof(remoteControlState))) == NULL )
	{
		perror("RemoteControl::Create");
		exit(1);
	}

	memset(state, 0, sizeof(remoteControlState));
	state->port = port;
	state->stats = stats;
	
	if ( pthread_mutex_init(&state->mutex, NULL) != 0 )
	{
		PRINTERROR("RemoteControl::CreateMutex");
		free(state);
		return 0;
	}

	return state;
}

void remotectrl_start(void* remotecontrol)
{
	remoteControlState* state = remotecontrol;

	if ( pthread_create(&state->thread, NULL, remotectrl_listenThread, state) != 0 )
	{
		perror("RemoteControl::Start");
		exit(1);
	}
}	

/** Sends log message over connected socket (if connection is available)
 @param message The message to send. Needs to be null terminated 
 @returns void
*/
void remotectrl_sendLogMessage(void* remotectrl, char* message)
{
	remoteControlState* state = remotectrl;
	char data = PROTOCOL_LOG_MESSAGE;

	pthread_mutex_lock(&state->mutex);

	if ( state->clientSocket != 0 )
	{
		if ( !socket_send(state->clientSocket, &data, 1) )
		{
			PRINTERROR("RemoteControl::SendLogMessage");
		}
		if ( !socket_send(state->clientSocket, message, strlen(message)) )
		{
			PRINTERROR("RemoteControl::SendLogMessage");
		}
		if ( !socket_send(state->clientSocket, "\r\n", 2) )
		{
			PRINTERROR("RemoteControl::SendLogMessage");
		}
	}

	pthread_mutex_unlock(&state->mutex);
}

void* remotectrl_listenThread(void* remotecontrol)
{
	remoteControlState* state = remotecontrol;
	int socket = 0;
	int clisock = 0;
	socklen_t clisize;	
	struct sockaddr_in sin_cliaddr;

	clisize	= sizeof(sin_cliaddr);

	if ( (socket = remotectrl_createServerSocket((u_short)state->port)) == -1 )
	{
		perror("RemoteControl::CreateServerSocket");
		return NULL;
	}

	state->listenSocket = socket;

	while ((clisock = accept(socket, (struct sockaddr*)&sin_cliaddr, &clisize)) > 0)
	{		
		state->clientSocket = clisock;
		remotectrl_handleClientSocket(remotecontrol);
	}
	
	return NULL;
}

void remotectrl_setFileReceivedCallback(void* remotectrl, void (*fileReceivedCallback)(void*, char*, char*, long))
{
	remoteControlState* state = remotectrl;

	state->fileReceivedCallback = fileReceivedCallback;
}

void remotectrl_setCommandCallback(void* remotectrl, void (*remoteCtrlCommand)(void*, char*, char*))
{
	remoteControlState* state = remotectrl;

	state->remoteControlCommand = remoteCtrlCommand;
}

void remotectrl_setUserData(void* remotectrl, void* userData)
{
	remoteControlState* state = remotectrl;

	state->userData = userData;
}

void remotectrl_setVersion(void* remotectrl, char* version)
{
	remoteControlState* state = remotectrl;

	strcpy(state->version, version);
}

/** 
	Handles client.
	Client can request information or send key/value pairs for commands
*/
void remotectrl_handleClientSocket(void* remotecontrol)
{
	remoteControlState* state = remotecontrol;
	int size = 0;
	void* buffer = socket_createBuffer();
	char* line = NULL;

	while ( (line = socket_receiveLine(state->clientSocket, buffer, &size, 0)) != NULL )
	{
		switch ( line[0] )
		{
			case PROTOCOL_COMMAND_STATS:
			{
				char sendbuffer[2048];
				char currentFile[1024];

				sprintf(sendbuffer, "Version=%s\r\nDownloadSpeed=%i\r\nIsPaused=%s\r\nProgress=%i\r\nFailedArticles=%i\r\n", 
					state->version,
					stats_getDownloadSpeed(state->stats), 
					stats_getIsPaused(state->stats)==1?"True":"False", 
					stats_getProgress(state->stats),
					stats_getFailedCount(state->stats));

				socket_send(state->clientSocket, sendbuffer, strlen(sendbuffer));

				stats_getCurrentNzbFile(state->stats, currentFile);

				sprintf(sendbuffer, "NzbFile=%s\r\n", currentFile);

				socket_send(state->clientSocket, sendbuffer, strlen(sendbuffer));

				break;
			}
			case PROTOCOL_COMMAND_SENDFILE:
			{
				char* space = strchr(line, ' ');
				char* newline = strchr(line, '\n');

				*newline = 0;

				if ( space == NULL )
				{
					//no conformance, disconnect to prevent client from sending its data anyway
					remotectrl_sendLogMessage(state, "RemoteControl::Invalid send file command syntax. Closing connection\n");
					CLOSESOCKET(state->clientSocket);
					state->clientSocket = 0;
				}
				else
				{
					long size = 0;
					char* filename = line+1;
					char path[1024];
					FILE* fp = NULL;
					int bytesread = 0;
					char buffer[2048];

					*space = 0;
					
					size = atol(space+1);

					if ( strlen(state->tmpDir) + strlen(filename) > 1024 )
					{
						//no conformance, disconnect to prevent client from sending its data anyway
						remotectrl_sendLogMessage(state, "RemoteControl::Filename to large. Closing connection\n");
						CLOSESOCKET(state->clientSocket);
						state->clientSocket = 0;
					}
					strcpy(path, state->tmpDir);
					strcat(path, filename);
				
					if ( (fp = fopen(path, "wb+")) == NULL )
					{
							//no conformance, disconnect to prevent client from sending its data anyway
						remotectrl_sendLogMessage(state, "RemoteControl::Could not create server side file. Closing connection\n");
						CLOSESOCKET(state->clientSocket);
						state->clientSocket = 0;
					}

					while ( size > 0 )
					{
						if ( !socket_receive(state->clientSocket, buffer, &bytesread, 1) )
						{
							break;
						}
						if ( fwrite(buffer, bytesread, 1, fp) <= 0 )
						{
							remotectrl_sendLogMessage(state, "RemoteControl::Could not write to server side file. Closing connection\n");
							CLOSESOCKET(state->clientSocket);
							state->clientSocket = 0;
						}
					}

					fclose(fp);
					
					remotectrl_sendLogMessage(state, "RemoteControl::File successfully received\n");

					if ( state->fileReceivedCallback != NULL )
						state->fileReceivedCallback(state->userData, filename, path, size);
				}

				break;
			}
			case PROTOCOL_COMMAND_GENERIC: /* user sends command (key=value pair) */
			{
				char* sep = strchr(line+1, '=');

				if ( sep != NULL )
				{
					char* key = malloc(MAX_COMMAND_KEY_SIZE);
					char* value = malloc(MAX_COMMAND_VALUE_SIZE);
					char* newline = strchr(line, '\n');
					
					*newline = 0;

					if ( key == NULL || value == NULL )
					{
						PRINTERROR("RemoteControl::HandleClientSocket");
						exit(1);
					}

					memset(key, 0, MAX_COMMAND_KEY_SIZE);
					memset(value, 0, MAX_COMMAND_VALUE_SIZE);

					if ( sep-line-1 < MAX_COMMAND_KEY_SIZE )
					{
						memcpy(key, line+1, sep-line-1);

						if ( strlen(line+(sep-line)+2) < MAX_COMMAND_VALUE_SIZE )
						{
							memcpy(value, line+(sep-line)+1, strlen(line+(sep-line)+2));

							if ( state->remoteControlCommand != NULL )
								state->remoteControlCommand(state->userData, key, value);
						}
					}

					free(key);
					free(value);
				}


				break;
			}
		}
	}

	CLOSESOCKET(state->clientSocket);
	state->clientSocket = 0;

	socket_freeBuffer(buffer);
}

void remotectrl_free(void* remotectrl)
{
	remoteControlState* state = remotectrl;

	CLOSESOCKET(state->clientSocket);
	CLOSESOCKET(state->listenSocket);
	free(state);
}

/** 
	Builds server socket
*/
int remotectrl_createServerSocket(u_short port)
{
	int srvSocket = 0;
	char reuse[4] = {1};
	struct sockaddr_in	sin_addr;

	sin_addr.sin_family = AF_INET;
	sin_addr.sin_addr.s_addr = htonl(INADDR_ANY);	
	sin_addr.sin_port = (u_short)htons(port);
	
	/* Create server socket */
	if ((srvSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket() function error");
		return -1;
	}
	
	if ((setsockopt(srvSocket, SOL_SOCKET, SO_REUSEADDR, reuse, sizeof(reuse))) < 0)
	{
		perror("setsockopt() function error");
		CLOSESOCKET(srvSocket);
		return -1;
	}
		
	/* Bind server socket */
	if ( bind(srvSocket, (struct sockaddr *)&sin_addr, sizeof(sin_addr)) < 0)
	{
		perror("bind() function error");
		CLOSESOCKET(srvSocket);
	 	return -1;
	} 

	/* Listen for clients */
	if (( listen(srvSocket, 10)) < 0)
	{
		perror("listen() function error");
		CLOSESOCKET(srvSocket);
		return -1;
	}

	return srvSocket;
}
